/**
 * 
 */
import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.*;

/**
 * @author bieber 
 * 请求实体
 */
public class Request {

	private Header header;

	private HttpURLConnection connection;

	private OutputStream out;

	private String urlStr;
	
	private String requestData;
	
	private InputStream requestInputStream;

	private static final String PROTOCOL_PREFIX = "http";

    private ContentType contentType;
    
    private CookieManager manager;


    public Request(String urlString, String parameterType) throws IOException {
        contentType= ContentType.getType(parameterType);
		this.urlStr = urlString;
	}

	/**
	 * 設置請求頭部信息
	 * @param key
	 * @param value
	 */
	public void setHeader(String key, String value) {
		if (header == null) {
			header = new Header();
		}
		header.put(key, value);
	}

	/**
	 * 執行請求，通過傳輸請求的類型（GET/POST）
	 * @param type
	 * @return
	 * @throws Exception
	 */
	private Response execute(String type) throws Exception {
        manager = new CookieManager();
        manager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
        CookieHandler.setDefault(manager);
		URL url = null;
		boolean isGet = false;
		if (type.equalsIgnoreCase("GET")) {
            if(requestData!=null&&!requestData.equals("")){
                int paramIndex = urlStr.indexOf("?");
                if(paramIndex>=0){
                    if(paramIndex<urlStr.length()) {
                        urlStr +="&"+requestData;
                    }else{
                        urlStr +=requestData;
                    }
                }else{
                    urlStr+="?"+requestData;
                }
            }
			url = new URL(urlStr);
			isGet = true;
		} else {
			url = new URL(urlStr);
		}
        if(urlStr.startsWith("https")){
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() },
                    new java.security.SecureRandom());
            HttpsURLConnection tempConnection = (HttpsURLConnection) url.openConnection();
            tempConnection.setSSLSocketFactory(sc.getSocketFactory());
            tempConnection.setHostnameVerifier(new TrustAnyHostnameVerifier());
            connection = tempConnection;
            fillHeader();
            connection.setDoInput(true);
            connection.setRequestMethod(type);
            //connection.connect();
        }else{
          //  Proxy proxy = new Proxy(Proxy.Type.HTTP,new InetSocketAddress("127.0.0.1",8081));
		    connection = (HttpURLConnection) url.openConnection();
            fillHeader();
            connection.setDoInput(true);
            connection.setRequestMethod(type);
            //connection.connect();
        }
		if (!isGet) {
            connection.setRequestMethod("POST");
            connection.setDoOutput(true);

			out = connection.getOutputStream();
			if(requestData!=null&&!requestData.equals("")){
				this.requestInputStream=new ByteArrayInputStream(requestData.getBytes());
			}
			writeRequestData();
		}
		return generateResponse();
	}

	public Response post(String data) throws Exception {
		this.requestData=data.replaceAll("\\\\","");
		return execute("POST");
	}

    public Response post() throws Exception {
        return execute("POST");
    }

	public Response post(Map<String, String> params) throws Exception {
		this.requestData=parseParamsMap(params);
		return execute("POST");
	}

	public Response post(InputStream is) throws Exception {
		this.requestInputStream=is;
		return execute("POST");
	}

	public Response post(byte[] bytes) throws Exception {//http://www.oschina.net
		this.requestInputStream=new ByteArrayInputStream(bytes);
		return execute("POST");
	}
	
	public Response get(String data) throws Exception {
		this.requestData=data;
		return execute("GET");
	}

	public Response get(Map<String, String> params) throws Exception {
		this.requestData=parseParamsMap(params);
		return execute("GET");
	}

	/**
	 * 將POST的數據提交到請求服務器
	 * @throws java.io.IOException
	 */
	private void writeRequestData() throws IOException {
		if(requestInputStream!=null){
			byte[] buffer = new byte[1024];
			int offset=-1;
			while((offset=requestInputStream.read(buffer, 0, 1024))>0){
				out.write(buffer, 0, offset);
			}
			out.flush();
			requestInputStream.close();
		}
	}

	/**
	 * 填充頭部信息
	 */
	private void fillHeader() throws MalformedURLException {
		connection.addRequestProperty("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1653.0 Safari/537.36");
        connection.addRequestProperty("Content-type",contentType.contentType);
        if(header!=null){
            for (Map.Entry<String, String> entry : header.entrySet()) {
                connection.addRequestProperty(entry.getKey(), entry.getValue());
            }
		}
	}

	/**
	 * 將KEY/VALUE的形式參數進行拼接成字符串參數
	 * @param params
	 * @return
	 */
	private String parseParamsMap(Map<String, String> params) throws UnsupportedEncodingException {
        if(params.size()<=0){
            return null;
        }
		StringBuffer paramsStr = new StringBuffer();
		for (Map.Entry<String, String> entry : params.entrySet()) {
			paramsStr.append(entry.getKey()).append("=")
					.append(URLEncoder.encode(entry.getValue(), "utf-8")).append("&");
		}
		paramsStr.setLength(paramsStr.length() - 1);
		return paramsStr.toString();
	}

	/**
	 * 創建請求的響應實體
	 * @return
	 * @throws java.io.IOException
	 */
	private Response generateResponse() throws IOException {
        InputStream is=null;
        try{
            is = connection.getInputStream();

            Response response= new Response(is);
            CookieStore cookieStore = manager.getCookieStore();
            List<HttpCookie> cookies =
                    cookieStore.getCookies();
            Header respHeader = new Header(connection.getHeaderFields());
            for (HttpCookie cookie: cookies) {
                Cookie temp = new Cookie();
                temp.setPath(cookie.getPath());
                temp.setDomain(cookie.getDomain());
                temp.setName(cookie.getName());
                temp.setValue(cookie.getValue());
                temp.setMaxAge(cookie.getMaxAge() + "");
                temp.setValue(cookie.getVersion() + "");
                respHeader.addCookies(temp);
                System.out.println("CookieHandler retrieved cookie: " + cookie);
            }
            response.setHeader(respHeader);
            response.setResponseCode(connection.getResponseCode() + "");
            return response;
        }catch(IOException e){
            Header respHeader = new Header(connection.getHeaderFields());
            Response response = new Response(connection.getResponseCode()+"",connection.getResponseMessage(),respHeader);
            return response;
        }finally{
            if(out!=null) {
                out.close();
            }
            connection.disconnect();
            if(is!=null){
                is.close();
            }
        }
	}

    enum ContentType{
        DEFAULT("www","application/x-www-form-urlencoded"),TEXT("text","text/plain;charset=UTF-8"),JSON("json","application/json;charset=UTF-8"),HTML("html","text/html;charset=UTF-8"),XML("xml","application/xml;charset=UTF-8");
        private String key;

        private String contentType;

        ContentType(String key,String contentType){
            this.key = key;
            this.contentType = contentType;
        }

        public static ContentType getType(String typeKey){
            ContentType[] contentTypes = ContentType.class.getEnumConstants();
            for(ContentType type:contentTypes){
                if(type.key.equalsIgnoreCase(typeKey)){
                    return type;
                }
            }
            return DEFAULT;
        }
    }
    private static class TrustAnyTrustManager implements X509TrustManager {

        public void checkClientTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        public void checkServerTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
        }

        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[] {};
        }
    }
    private static class TrustAnyHostnameVerifier implements HostnameVerifier {
        public boolean verify(String hostname, SSLSession session) {
            return true;
        }
    }

    public class Response {

        private static final String RESPONSE_CONTENT_TYPE_TOKEN="Content-Type";

        private Header header;

        private InputStream respInput;

        private String content;

        private String responseCode;

        private String contentType ;

        private OutputStream outputStream;
        //标示当前的response是否时空的，如果是空的，则说明该response还没有进行请求
        private volatile boolean isEmpty=true;

        public Response(InputStream input) throws IOException {
            respInput=input;
            getResponseContent();
            isEmpty=false;
        }
        public void setHeader(Header header){
            this.header=header;
            this.contentType = header.get(RESPONSE_CONTENT_TYPE_TOKEN);
        }
        public Response(){

        }

        public Response(String responseCode,String responseContent,Header header){
            this.responseCode = responseCode;
            this.content = responseContent;
            this.header = header;
        }

        public boolean isEmpty() {
            return isEmpty;
        }


        public void setOutputStream(OutputStream outputStream) {
            this.outputStream = outputStream;
        }

        public String getContentType() {
            return contentType;
        }

        public void copyFrom(Response response) throws IOException {
            this.header = response.getHeader();
            this.contentType = response.getContentType();
            this.content = response.getResponseContent();
            this.responseCode = response.getResponseCode();
            isEmpty=false;
        }

        public Header getHeader(){
            return header;
        }

        public String getResponseCode() {
            return responseCode;
        }

        public void setResponseCode(String responseCode) {
            this.responseCode = responseCode;
        }



        public String getResponseContent() throws IOException{
            if(content==null&&respInput!=null){
                ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
                byte[] buffer = new byte[1024];
                int offset=-1;
                while((offset=respInput.read(buffer, 0, 1024))>0){
                    byteBuffer.write(buffer, 0, offset);
                }
                respInput.close();
                content = new String(byteBuffer.toByteArray(), "UTF-8");
            }
            return content;
        }

        public String toString(){
            StringBuilder sb = new StringBuilder();
            sb.append("Response header:\n");
            sb.append(header!=null?header.toString():"");
            sb.append("Response body:\n");
            try {
                sb.append(getResponseContent());
            } catch (IOException e) {
               e.printStackTrace();
            }
            return sb.toString();
        }



    }
    class Header extends HashMap<String,String> {

        /**
         *
         */
        private static final long serialVersionUID = -5243834092301615429L;

        private Set<Cookie> cookies;

        private static final String ITEM_SPLIT="; ";

        private static final String COOKIE_KEY="Set-Cookie";

        public Header(){

        }

        public Header(Map<String,List<String>> headerFields){
            cookies = new HashSet<Cookie>();
            StringBuffer headerItemStr = new StringBuffer();
            for(Map.Entry<String,List<String>> entry:headerFields.entrySet()){
                headerItemStr.setLength(0); 
                if( COOKIE_KEY.equalsIgnoreCase(entry.getKey())){
                     continue;
                }else{
                    List<String> headerItems =  entry.getValue();
                    for(String header:headerItems) {
                        headerItemStr.append(header).append(ITEM_SPLIT);
                    }
                    headerItemStr.setLength(headerItemStr.length()-1);
                    put(entry.getKey(),headerItemStr.toString());
                }
            }
        }

        public String getHeader(String key){
            return get(key);
        }

        public Map<String,String> getHeaders(){
            return this;
        }
        
        public void addCookies(Cookie cookie){
            cookies.add(cookie);
        }

        public Set<Cookie> getCookies(){
            return Collections.unmodifiableSet(cookies);
        }

        public String toString(){
            StringBuffer sb = new StringBuffer();
            for(Map.Entry<String, String> entry:entrySet()){
                sb.append(entry.getKey()==null?"":entry.getKey()).append(":").append(entry.getValue()).append("\n");
            }
            sb.append(COOKIE_KEY).append(":").append(toCookiesString()).append("\n");
            return sb.toString();
        }

        public String toCookiesString(){
            StringBuffer cookiesStr = new StringBuffer();
            if(cookies!=null){
                for(Cookie cookie:cookies){
                    cookiesStr.append(cookie.getName()).append("=").append(cookie.getValue()).append(ITEM_SPLIT);
                }
            }
            if(cookiesStr.length()>0){
                cookiesStr.setLength(cookiesStr.length()-ITEM_SPLIT.length());
            }
            return cookiesStr.toString();
        }

    }

    class Cookie {

        private String expires;

        private String maxAge;

        private String path;

        private String domain;

        private String version;

        private String name;

        private String value;

        private static final String PATH_TOKEN="path";

        private static final String DOMAIN_TOKEN="domain";

        private static final String MAX_AGE_TOKEN="max-age";

        private static final String EXPIRES_TOKEN="expires";

        private static final String VERSION_TOKEN="version";

        public Cookie(){

        }

        public Cookie(String cookieStr){
            if(cookieStr!=null&&!cookieStr.equals("")){
                String[] splits = cookieStr.split(";");
                for(String item:splits){
                    int index = item.indexOf("=");
                    if(index<0){
                        continue;
                    }
                    String key =item.substring(0,index);
                    String value=item.substring(index+1);
                    if(key!=null&&!key.equals("")){
                        key=key.trim();
                        if(key.toLowerCase().equals(PATH_TOKEN)){
                            setPath(value.trim());
                        }else if(key.toLowerCase().equals(DOMAIN_TOKEN)){
                            setDomain(value.trim());
                        }else if(key.toLowerCase().equals(MAX_AGE_TOKEN)){
                            setMaxAge(value.trim());
                        }else if(key.toLowerCase().equals(EXPIRES_TOKEN)){
                            setExpires(value.trim());
                        }else if(key.toLowerCase().equals(VERSION_TOKEN)){
                            setVersion(value.trim());
                        }else{
                            setName(key);
                            String tempValue = value.trim();
                            if(!tempValue.startsWith("\"")||!tempValue.endsWith("\"")){
                                tempValue="\""+tempValue+"\"";
                            }
                            setValue(tempValue);
                        }
                    }
                }
            }
        }

        public String getExpires() {
            return expires;
        }

        public void setExpires(String expires) {
            this.expires = expires;
        }

        public String getMaxAge() {
            return maxAge;
        }

        public void setMaxAge(String maxAge) {
            this.maxAge = maxAge;
        }

        public String getPath() {
            return path;
        }

        public void setPath(String path) {
            this.path = path;
        }

        public String getDomain() {
            return domain;
        }

        public void setDomain(String domain) {
            this.domain = domain;
        }

        public String getVersion() {
            return version;
        }

        public void setVersion(String version) {
            this.version = version;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            Cookie cookie = (Cookie) o;

            if (name != null ? !name.equals(cookie.name) : cookie.name != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            return name != null ? name.hashCode() : 0;
        }

        @Override
        public String toString() {
            return "Cookie{" +
                    "expires='" + expires + '\'' +
                    ", maxAge='" + maxAge + '\'' +
                    ", path='" + path + '\'' +
                    ", domain='" + domain + '\'' +
                    ", version='" + version + '\'' +
                    ", name='" + name + '\'' +
                    ", value='" + value + '\'' +
                    '}';
        }
    }

}
